进阶 策略权限控制:基于函数的策略权限控制
在掌握了 JSON 基础条件和 MongoDB 查询操作符后,CASL 还提供第三种条件策略:PureAbility + 函数式条件匹配。这种方式允许将条件逻辑写成函数,实现最灵活的动态权限控制。CASL 官方不推荐在常规场景使用,但非常适合需要将条件序列化存入数据库的需求。
PureAbility 与 MongoAbility 的区别
import { createMongoAbility, PureAbility, AbilityBuilder } from '@casl/ability';
typescript
| 特性 | MongoAbility | PureAbility |
|---|---|---|
| 条件类型 | MongoDB 查询对象 | 自定义函数 |
| 灵活度 | 受限于操作符支持 | 任意 JavaScript 逻辑 |
| 序列化 | 可 JSON 序列化 | 需特殊处理 |
| 推荐场景 | 常规条件匹配 | 需要运行时动态逻辑 |
PureAbility 的函数条件
PureAbility 使用 MatchConditions 类型,允许第三个参数传入函数:
import { PureAbility, AbilityBuilder } from '@casl/ability';
import type { MatchConditions } from '@casl/ability';
const { can, build } = new AbilityBuilder(PureAbility);
// 第三个参数是一个函数,接收 subject 实例
can('read', 'Article', (article) => {
return article.authorId === userId;
});
const conditionsMatcher: MatchConditions = (match) => match;
return build({ conditionsMatcher });
typescript
函数的数据库存储与还原
核心问题
函数无法直接存入数据库。需要将函数拆分为参数列表和函数体字符串分别存储,读取后通过 new Function 还原。
存储方案
// 原始箭头函数
const originalFn = (authorId) => authorId === user.id;
// 拆分为可存储的部分
const args = ['user']; // 参数列表
const conditions = 'authorId === user.id'; // 函数体字符串
typescript
还原函数
// 使用 new Function 从字符串重建函数
const functionString = 'authorId === user.id';
const restoredFn = new Function('user', `return ${functionString}`);
// 执行时传入运行时参数
const result = restoredFn(currentUser);
// authorId 从 subject 实例获取,user 从运行时参数获取
typescript
等价关系说明
// 数据库存储形式:
// args: ["user"]
// conditions: "authorId === user.id"
// new Function 还原后等价于:
function restoredFunction(user) {
return authorId === user.id;
}
// 其中 authorId 来自 subject 实例属性
// user.id 来自运行时传入的参数
typescript
完整测试验证
import { PureAbility, AbilityBuilder } from '@casl/ability';
import type { MatchConditions } from '@casl/ability';
// 模拟从数据库读取的数据
const functionString = 'authorId === user.id';
const args = ['user'];
// 还原函数
const fn = new Function(...args, `return ${functionString}`);
// 构建 ability
const { can, build } = new AbilityBuilder(PureAbility);
const conditionsMatcher: MatchConditions = (match) => match;
const user = { id: 1 };
can('read', 'Article', () => fn(user));
const ability = build({ conditionsMatcher });
// 测试
const article1 = { authorId: 1 };
ability.can('read', article1); // true - authorId 匹配
const article2 = { authorId: 2 };
ability.can('read', article2); // false - authorId 不匹配
typescript
三种条件策略的整合
CaslAbilityService 条件策略体系
│
├─ 策略一: JSON 基础条件 (type=0)
│ 使用 createMongoAbility
│ 条件: { username: "tom1" }
│ 适用: 简单属性精确匹配
│
├─ 策略二: MongoDB 查询 (type=1)
│ 使用 createMongoAbility + buildMongoQueryMatcher
│ 条件: { $or: [{...}, {...}] }
│ 适用: 多字段组合逻辑
│
└─ 策略三: 函数条件 (type=2)
使用 PureAbility + new Function
条件: "authorId === user.id"
适用: 运行时动态变量绑定
text
CASL 官方建议
CASL 官方对函数条件的定位:
不推荐在常规场景使用函数条件。默认的对象条件匹配已经能满足绝大多数需求。函数条件主要用于需要将规则序列化到数据库的场景。
实际开发中,优先使用策略一和策略二。只有需要动态绑定运行时变量(如当前用户 ID)时才使用策略三。
数据库设计思考
整合三种策略后,数据库需要存储以下字段:
- type: 区分三种策略类型(0/1/2)
- conditions: 策略一存对象字符串,策略二存 JSON 查询字符串,策略三存函数体字符串
- args: 仅策略三需要,存储函数参数列表
下一节将把这些策略整合到统一的 CaslAbilityService 工厂方法中。
↑